import React, { forwardRef, useImperativeHandle, useEffect, useRef } from "react";
import type { ForwardRefExoticComponent, RefAttributes } from 'react';
import { Graph } from '@antv/x6';
import { pick } from 'lodash';
import { getRawData, extract, getCurrentApprove } from './utils';

import type { Cell } from "@antv/x6";
import type { Options as GraphOptions } from '@antv/x6/es/graph/options';
import type { Unit, ProcessNodeProps } from './components/ProcessNode';
import type { DagreLayoutOptions } from '@antv/layout';

import { DagreLayout } from '@antv/layout';
import ProcessNode from './components/ProcessNode';

import '@antv/x6-react-shape';
import register from "./register";
import './style/index.less';

register();

export type RawData = {
  // Unused
  activityId: number;
  finishDate: number | null;
  next: RawData | null;
  node: string;
  // Unused
  sourceActId: number;
  subSteps: RawData[];
  type: 'start' | 'stop' | 'SystemTask' | 'UserTask' | '';
  units: Unit[];
  identifier?: string;
}

export interface ParallerFlowProps {
  rawData: RawData;
  loginUser?: string;
  /*
  * A callback throws the processed data.
  * @param { Outputs } -An object containing graph, currentNodeName and CurrentApproversx
  * */
 getCurrent?: (outputs: Outputs) => void;
 isApprover?: (data: RawData) => boolean; // 判断当前登陆者是不是某个节点的审批者；可选，用于覆盖默认处理逻辑
 isApproved?: (data: RawData) => boolean; // 判断一个节点是否已经完成审批；可选，用于覆盖默认处理逻辑
 mustVerifyApprover?: boolean; // 当前审批节点审批人是否必须包含登陆用户；当为false时，待审批节点审批人都不包含登陆人时，会默认选中一个节点
 processNodeProps?: Omit<ProcessNodeProps, 'data' | 'current' | 'container' | 'isApproved'>;
// DagreLayout 支持修改的配置项
 dagreLayoutProps?: Pick<DagreLayoutOptions, 'rankdir' | 'align' | 'nodesep' | 'ranksep'>;
 graphProps?: Pick<Partial<GraphOptions.Manual>, 'width' | 'height' | 'autoResize' | 'scroller' | 'connecting'>
 layoutEdgeMap?: (data: LogicEdge, defaultEdgeConfig: any) => any;
//  自定义渲染节点之后，默认节点盒子大小计算逻辑可能有误，需要自定义节点盒子大小；
 getNodeSize?: (node: RawData) => { width: number; height: number };
}

export type ExtractFlowRef<T> = T extends ForwardRefExoticComponent<ParallerFlowProps & RefAttributes<infer P>> ? P : never;

export type Outputs = {
  graph: Graph;
  currentNodeName: string;
  currentApprovers: string[];
  currentNode: RawData;
}

type UseImp = {
  getCurrent: () => Outputs;
}

export type LogicEdge = {
  source: RawData;
  target: RawData;
};

const parallelFlow = forwardRef<UseImp, ParallerFlowProps>((props, ref) => {
  const {
    rawData,
    loginUser,
    getCurrent,
    isApproved,
    isApprover,
    mustVerifyApprover = true,
    processNodeProps = {},
    dagreLayoutProps = {},
    graphProps = {},
    layoutEdgeMap,
    getNodeSize
  } = props;
  const containerRef = useRef<HTMLDivElement>();
  const graphRef = useRef<Graph>();
  const currentNodeRef = useRef<RawData>();

  useImperativeHandle(
    ref,
    () => ({
      getCurrent: () => ({
        graph: graphRef.current!,
        currentNode: currentNodeRef.current,
        currentNodeName: currentNodeRef.current?.node,
        currentApprovers: currentNodeRef.current?.units.map((unit) => unit.user)
      })
   }));

   useEffect(() => {
     const container = containerRef.current;
     if(!rawData?.next || !container) return;
     const p = getRawData(rawData);
     const { nodes, edges } = extract(p);
     const layoutData: any = {};
     currentNodeRef.current = getCurrentApprove({
       nodes,
       edges,
       loginUser,
       isApproved,
       isApprover,
       mustVerifyApprover
     })

     layoutData.nodes = nodes.map((node: RawData) => {
       const size = getNodeSize?.(node) || {};
       return {
         id: node.identifier,
         width: 48 + node.node.length * 15,
         height: 32,
         ...pick(size, ['height', 'width']),
         shape: 'react-shape',
         component: (
           <ProcessNode
              {...processNodeProps}
              isApproved={isApproved}
              data={node}
              container={containerRef.current}
              current={node.identifier === currentNodeRef.current?.identifier}
           />
         )
       }
     });

     layoutData.edges = edges.map((options: LogicEdge) => {
       const { source, target } = options;

       const defaultEdgeConfig = {
         source: source?.identifier,
         target: target?.identifier,
         connector: 'rounded',
         attrs: {
           line: {
             stroke: 'rgba(0, 0, 0, .15)',
             strokeWidth: 1
           }
         }
       };
       if (typeof layoutEdgeMap === 'function') return layoutEdgeMap(options, defaultEdgeConfig);

       return defaultEdgeConfig;
     });

     if(graphRef.current) graphRef.current?.dispose();

     graphRef.current = new Graph({
       scroller: {
         enabled: true,
         pannable: true,
         padding: 150,
       } as any,
       autoResize: true,
       height: 400,
       ...pick(graphProps, ['width', 'height', 'autoResize', 'scroller']),
       connecting: {
        // 节点锚点设置
        anchor: 'iconCenter',
        // 节点连接路由设置
        router: {
          name: 'twoSide',
          args: {
            step: 41
          }
        },
        ...pick(graphProps.connecting, ['anchor', 'router'])
       },
       container,
       interacting: false
     });

     if(getCurrent){
       getCurrent({
         graph: graphRef.current,
         currentNode: currentNodeRef.current,
         currentNodeName: currentNodeRef.current?.node,
         currentApprovers: currentNodeRef.current?.units.map((unit) => unit.user)
       })
     }

     const dagreLayout = new DagreLayout({
       type: 'dagre',
       rankdir: 'LR',
       align: 'UL',
       ranksep: 90,
       nodesep: 70,
       ...pick(dagreLayoutProps, ['rankdir', 'align', 'nodesep', 'ranksep'])
     });

     const model = dagreLayout.layout(layoutData);
     const { x: currentX, y: currentY } = layoutData.nodes.find(
       (item: Cell) => item.id === String(currentNodeRef.current?.identifier)
     ) ?? {};
     graphRef.current.fromJSON(model);
     if(currentX && currentY){
       graphRef.current.scrollToPoint(currentX, currentY + 180);
     }
   }, [rawData, loginUser, getCurrent]);

   return (
     <div className="flow-container">
       <div
        className="parallel-flow"
        ref={containerRef}
        style={{ overflow: 'auto', flex: 1, height: graphProps.height || 400 }}
       />
     </div>
   )
})

export default React.memo(parallelFlow);
